<template>
  <div class="virtual-list  relative z-10" ref="visibleAreaRef" :style="`height:${visibleCount * itemHeight}px`">
    <div class="absolute w-full top-0 left-0 right-0">
      <div class="phantom w-full  relative" ref="phantomRef">
        <div :style="`top:${(visiBleOptions.startIndex + 1) * itemHeight - 3}px`" class="gard-top absolute"
          ref="gardTopRef" />
        <div class="absolute" :style="`top:${(visiBleOptions.startIndex + index) * itemHeight}px`"
          v-for="(item, index) in visibleItem" :key="index" :ref="(el: any) => cardEls[index] = el">
          <slot :row="item" />
        </div>
        <div class="gard-buttom absolute" ref="gardButtomRef"
          :style="`top:${(visiBleOptions.endIndex - 1) * itemHeight}px`" />
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import {
  ref,
  computed,
  reactive,
  onMounted,
  nextTick,
  watchEffect,
  onBeforeUnmount
} from 'vue'

/* ---------- props ---------- */
const props = defineProps({
  data: { type: Array, default: () => [] },
  visibleCount: { type: Number, default: 5 },
})

/* ---------- DOM refs ---------- */
const visibleAreaRef = ref<HTMLElement>()
const phantomRef = ref<HTMLElement>()
const gardTopRef = ref<HTMLElement>()
const gardButtomRef = ref<HTMLElement>()
const cardEls = ref<HTMLElement[]>([])

/* ---------- 可视数据 ---------- */
const visiBleOptions = reactive({ startIndex: 0, endIndex: props.visibleCount * 2 })
const visibleItem = computed(() =>
  props.data.slice(visiBleOptions.startIndex, visiBleOptions.endIndex)
)

/* ---------- 初始化 phantom 高度/表格宽度 ---------- */
const itemHeight = computed(() => cardEls.value[0]?.getBoundingClientRect().height)
const listWidth = computed(() => cardEls.value[0]?.getBoundingClientRect().width)
onMounted(async () => {
  await nextTick()
  const firstEl = cardEls.value[0]
  if (!firstEl) return
  phantomRef.value!.style.height = `${props.data.length * itemHeight.value}px`
})

/* ---------- 工具：创建 observer ---------- */
function makeObserver(cb: IntersectionObserverCallback) {
  return new IntersectionObserver(cb, {
    root: visibleAreaRef.value,
    threshold: 0.2
  })
}
let ioTop: IntersectionObserver | null = null
let ioBtm: IntersectionObserver | null = null

/* ---------- 建立 / 销毁监听 ---------- */
onMounted(() => {
  watchEffect((onCleanup) => {
    nextTick(() => {
      ioTop?.disconnect()
      ioBtm?.disconnect()

      ioTop = makeObserver(([e]) => {
        if (!e.isIntersecting || visiBleOptions.startIndex === 0)
          return
        visiBleOptions.startIndex = Math.max(0, visiBleOptions.startIndex - Math.ceil(props.visibleCount / 2))
        visiBleOptions.endIndex = Math.min(props.data.length, visiBleOptions.endIndex - Math.ceil(props.visibleCount / 2))
      })
      ioBtm = makeObserver(([e]) => {
        if (!e.isIntersecting || visiBleOptions.endIndex === props.data.length) return
        visiBleOptions.startIndex = Math.min(
          props.data.length - visibleItem.value.length,
          visiBleOptions.startIndex + Math.ceil(props.visibleCount / 2)
        )
        visiBleOptions.endIndex = Math.min(
          props.data.length,
          visiBleOptions.endIndex + Math.ceil(props.visibleCount / 2)
        )
      })

      if (gardTopRef.value) ioTop.observe(gardTopRef.value)
      if (gardButtomRef.value) ioBtm.observe(gardButtomRef.value)
    })

    onCleanup(() => {
      ioTop?.disconnect()
      ioBtm?.disconnect()
    })
  })
})

onBeforeUnmount(() => {
  ioTop?.disconnect()
  ioBtm?.disconnect()
})
</script>

<style scoped>
.virtual-list {
  width: v-bind('`${listWidth + 7}px`');
  overflow-y: auto;
}

.gard-top,
.gard-buttom {
  height: 1px;
  width: 100%;
}
</style>